#!/usr/bin/perl -w

my $usageStr = <<END_USAGE;
dv-tcp-client [--help/-h] <host> <port>
    --help/-h    displays this help text
    <host>       TCP server hostname or IP address
    <port>       TCP server port
END_USAGE

# Simple replacement for "netcat" (or "socat TCP:<host>:<port>"),
# connecting as a raw TCP client to the given host and port, sending
# port output to standard output and sending standard input to the
# port.

use strict;
use warnings;

use Socket;
use Fcntl;

use constant SYSWRITE_ALL_WAIT_SECS => 0.01;

sub usage {
    my ($msg) = @_;
    print STDERR "$msg\n\n" if $msg;
    print STDERR $usageStr;
    exit 1;
}


# Same as perl syswrite, except this detects incomplete writes (as can
# happen when a syscall is interrupted) and retries them until all
# data is written (or an error is encountered).
#
# Args: (same as Perl syswrite)
#   $handle - filehandle to be written
#   $buf    - data to be written
#   $bytes  - optional number of bytes to be written
#   $offset - optional offset within $buf to start
# Returns:
#   number of bytes actually written, or
#   undef if syswrite error (and $! set appropriately)
#
sub syswriteAll {
    my ($handle, $buf, $bytes, $offset) = @_;
    $bytes = length($buf) unless (defined $bytes);
    $offset = 0 unless (defined $offset);
    $bytes += $offset;
    while ($offset < $bytes) {
	my $n = syswrite($handle, $buf, $bytes - $offset, $offset);
	return unless (defined $n);
	$offset += $n;
	# Sleep a little if we made no progress
	select(undef, undef, undef, SYSWRITE_ALL_WAIT_SECS) unless $n;
    }
    return $bytes;
}


######################################################################
#			    TCP Client
######################################################################

# Parse the command line
for my $arg (@ARGV) {
    if ($arg eq "--help" || $arg eq "-h") {
	usage();
    }
}
usage() unless (@ARGV == 2);
my ($host, $port) = @ARGV;

my $tcpProtocol = getprotobyname("tcp");

socket(SOCK, Socket::PF_INET, Socket::SOCK_STREAM, $tcpProtocol)
    or die "socket: $!";
my $ipAddr = gethostbyname($host)
    or die "cannot find IP address for host '$host'\n";
connect(SOCK, sockaddr_in($port, $ipAddr)) or die "connect: $!";
fcntl(SOCK, Fcntl::F_SETFL, Fcntl::O_NONBLOCK) or die "fcntl: $!";

my $rin = "";
vec($rin, fileno(SOCK), 1) = 1;
vec($rin, fileno(STDIN), 1) = 1;
while (1) {
    my $rout = $rin;
    my $nr = select($rout, undef, undef, undef);
    die "select: $!" unless defined $nr;
    if (vec($rout, fileno(SOCK), 1)) {
	# Propagate SOCK output to STDOUT
	my $bytes = sysread(SOCK, my $buf, 4096);
	if ($bytes) {
	    syswriteAll(\*STDOUT, $buf, $bytes) or die "syswrite: $!";
	} else {
	    # server disconnected
	    exit(0);
	}
    } elsif (vec($rout, fileno(STDIN), 1)) {
	# Propagate STDIN output to SOCK
	my $bytes = sysread(STDIN, my $buf, 4096);
	if ($bytes) {
	    syswriteAll(\*SOCK, $buf, $bytes) or die "syswrite: $!";
	} else {
	    # STDIN closed
	    exit(0);
	}
    }
}
